package noyau;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import plugin.Plugin;

/** Noyau de l'application. */

public class Noyau {

    /** Map qui associe l'identifiant d'un plugin a son instance. */
    private Map < String, Plugin > cleObjet;

    /**
     * Un logger global au noyau
     */
    private static final Logger LOG = Logger.getLogger("noyau");

    /** Les informations de configuration. */
    private Configuration configuration;

    /** La strategie de chargement pour charger et instancier les classes. */
    private StrategieChargement chargement;

    /** Creer le noyau en utilisant une strategie par defaut.
     * @param configuration la configuration que doit mettre en place le noyau
     */
    public Noyau(final Configuration configuration) {
        // On applique une stratégie par défaut
        this(configuration, new StrategieChargementImpl());
    }

    /** Creer le noyau en précisant la stratégie de chargement des classes.
     * @param configuration la configuration, l'architecture que doit mettre en place le noyau
     * @param chargement permet de preciser une stratégie de chargement des classes
     */
    public Noyau(final Configuration configuration, final StrategieChargement chargement) {
        this.cleObjet = new HashMap < String, Plugin >();

        this.configuration = configuration;
        this.configurerLogger();

        LOG.log(Level.INFO,   "==================================\n"
                            + "==== NOUVELLE CONSTRUCTION DU NOYAU ====\n"
                            + "========================================");

        this.chargement = chargement;

        if (!configuration.verificationFichier()) {
            LOG.log(Level.WARNING, "Erreur dans le fichier de configuration.\n"
                    + "Branchement de plug-ins non-déclarés");
        }

        this.chargerPlugins();
    }

    /** Configuration du logger.
     * creation d'un fichier associe
     */
    private void configurerLogger() {
        LOG.setLevel(Level.FINEST);
        try {
            FileHandler handlerFichierLog =
                new FileHandler(configuration.getFichierlog(), true);
            LOG.addHandler(handlerFichierLog);
            SimpleFormatter formatter = new SimpleFormatter();
            handlerFichierLog.setFormatter(formatter);
        } catch (SecurityException e) {
            System.out.println("Erreur lors de la creation du fichier log "
                    + configuration.getFichierlog()
                    + "\n"
                    + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("Erreur lors de la creation du fichier log "
                    + configuration.getFichierlog()
                    + "\n"
                    + e.getMessage());
        }
    }

    /**
     * On instancie tous les plugins connects en remplissant la map
     */
    private void chargerPlugins() {
        for (String identifiant : configuration.getDeclares()) {
            // Pour chaque plugin dclar...
            // on essai de crer l'instance
            try {
                Object plugin = chargement.getInstance(
                                      configuration.getNomClasse(identifiant));
                // on ajoute l'instance  la map
                cleObjet.put(identifiant, (Plugin) plugin);
            } catch (InstantiationException e) {
                LOG.log(Level.WARNING, "La classe "
                        + configuration.getNomClasse(identifiant)
                        + " n'est pas instanciable\n"
                        + e.getMessage());
            } catch (IllegalAccessException e) {
                LOG.log(Level.WARNING, "La classe "
                        + configuration.getNomClasse(identifiant)
                        + " n'est pas instanciable."
                        + "Le constructeur vide est-il bien public ?\n"
                        + e.getMessage());
            } catch (ClassNotFoundException e) {
                LOG.log(Level.WARNING, "La classe "
                        + configuration.getNomClasse(identifiant)
                        + " n'est pas connue\n"
                        + e.getMessage());
            }
        }
    }

    /**
     * Pour chaque plugin p, appeler p.init()
     */
    private void initialiserPlugins() {
        LOG.log(Level.INFO, "Initialisation des plugins (init)");
        for (Plugin p : cleObjet.values()) {
            p.init(this);
        }
    }

    /**
     * Pour chaque plugin p, appeler p.run()
     */
    private void lancerPlugins() {
        LOG.log(Level.INFO, "Lancement des plugins (run)");
        for (Plugin p : cleObjet.values()) {
            p.run();
        }
    }

    /** Lancer le noyau. */
    public final void run() {
        LOG.log(Level.INFO, "Lancement du noyau");
        this.initialiserPlugins();
        this.lancerPlugins();
    }

    /** Permet a un plugin instancie de savoir quel est son identifiant.
     * @param plugin une reference vers l'instance d'un plugin en cours d'execution
     * @return l'identifiant attribu dans le fichier de configuration
     */
    public final String recupererNomVariable(final Plugin plugin) {
        String resultat = null;
        for (String nomVariable : cleObjet.keySet()) {
            if (plugin == cleObjet.get(nomVariable)) {
                resultat = nomVariable;
            }
        }
        return resultat;
    }

    /** Permet  un plugin de recuperer tous les plugins connects qui implementent une interface.
     * @param nomInterface le nom de l'interface
     * @param appelant le plugin appelant (en general, on passe this)
     * @return une liste d'objects : l'appelant devra les caster en nomInterface
     */
    public final List < Plugin > recupererPluginsConnectes(final String nomInterface, final Plugin appelant) {
        LOG.log(Level.INFO, "Le plugin \""
                + recupererNomVariable(appelant)
                + "\" demande les plugins connects qui implmentent "
                + nomInterface);

        final List < Plugin > resultat = new ArrayList < Plugin >();
        try {
            // On recupere tous les plugins qui sont branches
            // a l'appelant : ils sont candidats
            final List < String > pluginsConnectes =
                configuration.getConnectes(
                    recupererNomVariable(appelant)
                );

            for (String idPluginCandidat : pluginsConnectes) {
                Plugin pluginCandidat = cleObjet.get(idPluginCandidat);
                Class < ? >  cl = chargement.getClasse(nomInterface);
                if (cl.isAssignableFrom(pluginCandidat.getClass())) {
                    // Le plugin candidat implmente bien l'interface demande
                    resultat.add(pluginCandidat);
                }
            }
        } catch (ClassNotFoundException e) {
            LOG.log(Level.WARNING, "L'interface "
                    + nomInterface
                    + " n'est pas connue par le noyau\n"
                    + e.getMessage());
        }
        return resultat;
    }
}
